#include "global.h"

#define FALLSPEED 400    // milliseconds between each drop

#define DIRECTION_DOWN  1
#define DIRECTION_UP    2
#define DIRECTION_RIGHT 3
#define DIRECTION_LEFT  4
#define DIRECTION_FALL  5

// global variables
float camXAngle;
float camYAngle;
float camZoom;
int falltime = FALLSPEED;
int movetime = 150;
bool gameover;

void InitGame(OBJECT go[])
{
    srand(GetTickCount());

    camZoom = 20.0f;
    camXAngle = 0.0f;
    camYAngle = 0.0f;

    for(int Index = 0; Index < 1024; Index++)
    {
        go[Index].falling = false;
        go[Index].bricktype = 0;
    }

    NewBlock(go);

    gameover = false;

    return;
}

void Logic(OBJECT go[], INPUTDATA* InputData)
{
    static int time, start_time = GetTickCount();
    time = GetTickCount() - start_time;
    start_time = GetTickCount();

    // for every millisecond...
    for(int ms = 0; ms < time; ms++)
    {
        // handle timers
        falltime--;
        movetime--;

        // move all falling blocks down if the timer has triggered
        if(falltime < 0)
        {
            MoveBlock(go, DIRECTION_FALL);
            falltime = FALLSPEED;
        }
    }

    // adjust the camera based on the mouse movements
    camXAngle -= InputData->MouseX / 200.0f;
    camYAngle += InputData->MouseY / 200.0f;
    camZoom -= InputData->MouseZ / 50.0f;

    AdjustCamera(cos((double)camXAngle) * camZoom * cos((double)camYAngle),
                 sin((double)camYAngle) * camZoom,
                 sin((double)camXAngle) * camZoom * cos((double)camYAngle));

    // if the game was lost last frame, reset the game
    if(gameover)
        InitGame(go);

    // handle movement
    if(InputData->MoveDown && movetime < 0)
    {
        MoveBlock(go, DIRECTION_DOWN);
        movetime = 150;
    }
    if(InputData->MoveUp && movetime < 0)
    {
        MoveBlock(go, DIRECTION_UP);
        movetime = 150;
    }
    if(InputData->MoveRight && movetime < 0)
    {
        MoveBlock(go, DIRECTION_RIGHT);
        movetime = 150;
    }
    if(InputData->MoveLeft && movetime < 0)
    {
        MoveBlock(go, DIRECTION_LEFT);
        movetime = 150;
    }
    if(InputData->Drop && movetime < 0)
    {
        DropBlock(go);
        movetime = 150;
    }
    if(InputData->Rotate && movetime < 0)
    {
        RotateBlock(go);
        movetime = 150;
    }

    return;
}

void RotateBlock(OBJECT go[])
{
    bool moveallowed = true;
    int bricktype = 0;
    int startbrick = 0;

    // mark the brick to use as the pivot point
    for(int Index = 0; Index < 637; Index++)
    {
          if(go[Index].falling)
          {
                startbrick = Index;
                bricktype = go[Index].bricktype;
                break;
          }
    }

    // find the new brick locations and mark them
    for(int Index = 0; Index < 637; Index++)
    {
          if(go[Index].falling)
          {
                int newposition;
                newposition = ((Index - startbrick) % 49) * 7;
                newposition += startbrick;
                newposition += (Index - startbrick) / 49;
                go[newposition].movedto = true;

                if(Index > 587)
                    moveallowed = false;
          }
    }

    // Check to see if any movedto blocks have moved through walls
    for(int Index = 0; Index < 637; Index++)
    {
          if(go[Index].movedto == true && go[Index + 7].movedto == true)
                if((Index / 49) != ((Index + 7) / 49))
                      moveallowed = false;

          if(go[Index].movedto == true && go[Index + 1].movedto == true)
                if((Index / 7) != ((Index + 1) / 7))
                      moveallowed = false;
    }

    // Check to see if any movedto blocks already exist as non-falling blocks
    for(int Index = 0; Index < 637; Index++)
    {
        if(go[Index].movedto == true
           && go[Index].falling == false
           && go[Index].bricktype > 0)
        {
            moveallowed = false;
        }
    }

    // erase all falling blocks
    if(moveallowed)
    {
        for(int Index = 0; Index < 637; Index++)
        {
            if(go[Index].falling)
            {
                go[Index].bricktype = 0;
                go[Index].falling = false;
            }
        }
    }

    // set all movedto blocks
    if(moveallowed)
    {
        for(int Index = 0; Index < 637; Index++)
        {
            if(go[Index].movedto)
            {
                go[Index].bricktype = bricktype;
                go[Index].falling = true;
            }
        }
    }

    // reset all movedto values to false
    for(int Index = 0; Index < 637; Index++)
        go[Index].movedto = false;

    return;
}

// drops the block to the lowest space available
void DropBlock(OBJECT go[])
{
    bool stillfalling = true;
    bool moveallowed = true;
    int bricktype = 0;
    int falltime = 0;

    // Find the lowest point to fall to
    int Drop = 13;    // counts the distance to fall
    for(int Index = 0; Index < 637; Index++)
    {
        if(go[Index].falling)
        {
            for(int Index2 = Index; Index2 < 637; Index2+=49)
            {
                if((go[Index2].falling == false && go[Index2].bricktype >> 0))
                {
                    if(Drop > (Index2 - Index) / 49)
                        Drop = (Index2 - Index) / 49;

                    break;
                }

                if(Index2 > 588)
                {
                    if(Drop > (Index2 - Index + 49) / 49)
                        Drop = (Index2 - Index + 49) / 49;

                    break;
                }
            }
        }
    }

    // Fall however many spaces were counted
    for(int Index = 0; Index < Drop; Index++)
        MoveBlock(go, 5);

    return;
}

// moves the block in the specified direction
void MoveBlock(OBJECT go[], int direction)
{
    bool moveallowed = true;
    int bricktype = 0;
    int falltime = 0;

    // Set movedto values and detect if bricks are moving into walls or floor
    for(int Index = 0; Index < 637; Index++)
    {
        if(go[Index].falling)
        {
            bricktype = go[Index].bricktype;
            falltime = go[Index].falltime;
            switch(direction)
            {
            case DIRECTION_DOWN:
                {
                    if(go[Index].falling)
                    {
                        go[Index + 1].movedto = true;
                        if((Index / 7) != ((Index + 1) / 7))
                            moveallowed = false;
                    }
                } break;

            case DIRECTION_UP:
                {
                    if(go[Index].falling)
                    {
                        go[Index - 1].movedto = true;
                        if((Index / 7) != ((Index - 1) / 7))
                            moveallowed = false;
                        if(Index < 1)
                            moveallowed = false;
                    }
                } break;

                  case DIRECTION_RIGHT:
                {
                    if(go[Index].falling)
                    {
                        go[Index + 7].movedto = true;
                        if((Index / 49) != ((Index + 7) / 49))
                            moveallowed = false;
                    }
                } break;

            case DIRECTION_LEFT:
                {
                    if(go[Index].falling)
                    {
                        go[Index - 7].movedto = true;
                        if((Index / 49) != ((Index - 7) / 49))
                            moveallowed = false;
                        if(Index < 7)
                            moveallowed = false;
                    }
                } break;

            case DIRECTION_FALL:
                {
                    if(go[Index].falling)
                    {
                        go[Index + 49].movedto = true;
                        if(Index > 587)
                            moveallowed = false;
                    }
                } break;
            }
        }
    }

    // Check to see if any movedto bricks already exist as non-falling bricks
    for(int Index = 0; Index < 637; Index++)
    {
        if(go[Index].movedto == true
           && go[Index].falling == false
           && go[Index].bricktype > 0)
        {
            moveallowed = false;
        }
    }

    // erase all falling bricks
    if(moveallowed)
    {
        for(int Index = 0; Index < 637; Index++)
        {
            if(go[Index].falling)
            {
                go[Index].bricktype = 0;
                go[Index].falling = false;
            }
        }
    }

    // set all movedto bricks
    if(moveallowed)
    {
        for(int Index = 0; Index < 637; Index++)
        {
            if(go[Index].movedto)
            {
                go[Index].bricktype = bricktype;
                go[Index].falling = true;
            }
        }
    }

    // reset all movedto values to false
    for(int Index = 0; Index < 637; Index++)
        go[Index].movedto = false;

    // if a brick landed (while moving down) make a new block and turn off the old brick
    if(direction == 5 && moveallowed == false)
    {
        // turn off falling
        for(int Index = 0; Index < 637; Index++)
            go[Index].falling = false;

        // detect any full rows and delete them
        DeleteFullLayers(go);

        // make new block
        NewBlock(go);
    }

    return;
}

// creates a new block
void NewBlock(OBJECT go[])
{
    int bricktype = 0;
    int d100 = rand() % 99 + 1;

    // roll a number from 1 to 100
    if(d100 <= 15) bricktype = 1;
    if(d100 <= 30 && d100 > 15) bricktype = 2;
    if(d100 <= 45 && d100 > 30) bricktype = 3;
    if(d100 <= 53 && d100 > 45) bricktype = 4;
    if(d100 <= 68 && d100 > 53) bricktype = 5;
    if(d100 <= 83 && d100 > 68) bricktype = 6;
    if(d100 <= 88 && d100 > 83) bricktype = 7;
    if(d100 <= 98 && d100 > 88) bricktype = 8;
    if(d100 > 98) bricktype = 9;

    switch(bricktype)
    {
    case 1:
        {
            NewBrick(&go[0], bricktype);
        } break;
    case 2:
        {
            NewBrick(&go[0], bricktype);
            NewBrick(&go[1], bricktype);
        } break;
    case 3:
        {
            NewBrick(&go[0], bricktype);
            NewBrick(&go[1], bricktype);
            NewBrick(&go[2], bricktype);
        } break;
    case 4:
        {
            NewBrick(&go[0], bricktype);
            NewBrick(&go[1], bricktype);
            NewBrick(&go[2], bricktype);
            NewBrick(&go[3], bricktype);
        } break;
    case 5:
        {
            NewBrick(&go[0], bricktype);
            NewBrick(&go[1], bricktype);
            NewBrick(&go[49], bricktype);
            NewBrick(&go[50], bricktype);
        } break;
    case 6:
        {
            NewBrick(&go[0], bricktype);
            NewBrick(&go[1], bricktype);
            NewBrick(&go[7], bricktype);
            NewBrick(&go[8], bricktype);
            NewBrick(&go[49], bricktype);
            NewBrick(&go[50], bricktype);
            NewBrick(&go[56], bricktype);
            NewBrick(&go[57], bricktype);
        } break;
    case 7:
        {
            NewBrick(&go[0], bricktype);
            NewBrick(&go[1], bricktype);
            NewBrick(&go[2], bricktype);
            NewBrick(&go[7], bricktype);
            NewBrick(&go[8], bricktype);
            NewBrick(&go[9], bricktype);
            NewBrick(&go[49], bricktype);
            NewBrick(&go[50], bricktype);
            NewBrick(&go[51], bricktype);
            NewBrick(&go[56], bricktype);
            NewBrick(&go[57], bricktype);
            NewBrick(&go[58], bricktype);
        } break;
    case 8:
        {
            NewBrick(&go[0], bricktype);
            NewBrick(&go[1], bricktype);
            NewBrick(&go[2], bricktype);
            NewBrick(&go[49], bricktype);
            NewBrick(&go[50], bricktype);
            NewBrick(&go[51], bricktype);
            NewBrick(&go[98], bricktype);
            NewBrick(&go[99], bricktype);
            NewBrick(&go[100], bricktype);
        } break;
    case 9:
        {
            NewBrick(&go[0], bricktype);
            NewBrick(&go[1], bricktype);
            NewBrick(&go[2], bricktype);
            NewBrick(&go[7], bricktype);
            NewBrick(&go[8], bricktype);
            NewBrick(&go[9], bricktype);
            NewBrick(&go[14], bricktype);
            NewBrick(&go[15], bricktype);
            NewBrick(&go[16], bricktype);
            NewBrick(&go[49], bricktype);
            NewBrick(&go[50], bricktype);
            NewBrick(&go[51], bricktype);
            NewBrick(&go[56], bricktype);
            NewBrick(&go[57], bricktype);
            NewBrick(&go[58], bricktype);
            NewBrick(&go[63], bricktype);
            NewBrick(&go[64], bricktype);
            NewBrick(&go[65], bricktype);
            NewBrick(&go[98], bricktype);
            NewBrick(&go[99], bricktype);
            NewBrick(&go[100], bricktype);
            NewBrick(&go[105], bricktype);
            NewBrick(&go[106], bricktype);
            NewBrick(&go[107], bricktype);
            NewBrick(&go[112], bricktype);
            NewBrick(&go[113], bricktype);
            NewBrick(&go[114], bricktype);
        } break;
    default:
        NewBrick(&go[1], 1);
    }

    return;
}

// creates a new brick
void NewBrick(OBJECT* block, int bricktype)
{
    if(block->bricktype > 0)
        gameover = true;

    block->bricktype = bricktype;
    block->falling = true;
}

// detects for any layers that are full and deletes them
void DeleteFullLayers(OBJECT go[])
{
    // for each row...
    for(int start = 0; start < 637; start += 49)
    {
        bool layerfull = true;

        // detect if it is full
        for(int Index = start; Index < start + 49; Index++)
        {
            if(go[Index].bricktype == 0)
                layerfull = false;
        }

        // and delete it if it is
        if(layerfull)
            DeleteLayer(go, start / 49);
    }

    return;
}

// deletes the specified layer
void DeleteLayer(OBJECT go[], int row)
{
    // delete the indicated row
    for(int Index = row * 49; Index < (row + 1) * 49; Index++)
    {
        go[Index].bricktype = 0;
        go[Index].falling = false;
    }

    // shift bricks down
    for(int Index = row * 49 - 1; Index > 0; Index--)
    {
        go[Index + 49] = go[Index];
    }

    return;
}